﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics.Contracts;

namespace AppToolkit.WPF.UI
{
    /// <summary>
    /// Extended functionality around walking the visual tree in WPF.
    /// </summary>
    public static class VisualTreeHelperExt
    {
        /// <summary>
        /// Walks the visual tree until it finds an element of the given <see cref="Type"/> type.
        /// </summary>
        /// <param name="dp">element to start tree walk from</param>
        /// <param name="type">element type to search for.</param>
        /// <returns>the instance of the element that was found, null otherwise</returns>
        public static object GetParentOfType(DependencyObject dp, Type type)
        {
            Contract.Requires<ArgumentNullException>(dp != null, "sp");
            Contract.Requires<ArgumentOutOfRangeException>(typeof(FrameworkElement).IsAssignableFrom(type), 
                "type needs to derive from FrameworkElement");

            DependencyObject current = dp;
            do
            {
                if (current.GetType() == type)
                {
                    return current;
                }
            }
            while ((current = VisualTreeHelper.GetParent(current)) != null);
            return null;
        }

        /// <summary>
        /// Walks the visual tree until it finds an element of given type TItem.
        /// </summary>
        /// <param name="dp">element to start tree walk from</param>
        /// <typeparam name="TItem">element type to search for.</typeparam>
        /// <returns>the instance of the element that was found, null otherwise</returns>
        public static TItem GetParentOfType<TItem>(DependencyObject dp)
            where TItem : FrameworkElement
        {
            Contract.Requires<ArgumentNullException>(dp != null);

            DependencyObject current = dp;
            do
            {
                var ti = current as TItem;
                if (ti != null)
                {
                    return ti;
                }
            }
            while ((current = VisualTreeHelper.GetParent(current)) != null);
            return default(TItem);
        }

        /// <summary>
        /// Walks the Visual Tree from leave to root and checks for matching predicates. In this case 
        /// the corresponding action is called and the walk is aborted.
        /// </summary>
        /// <param name="dp">visual element to start the walk with</param>
        /// <param name="filterPipe">initial element of filter pipe</param>
        /// <returns></returns>
        public static bool WalkTree(DependencyObject dp, PredicateAction filterPipe)
        {
            Contract.Requires<ArgumentNullException>(dp != null);

            DependencyObject current = dp;
            do
            {
                if (filterPipe.Execute(current))
                    return true;
            }
            while ((current = VisualTreeHelper.GetParent(current)) != null);
            return false;
        }

        /// <summary>
        /// Walks the VisualTree to find the Root Window.
        /// </summary>
        /// <param name="leaveItem"></param>
        /// <returns></returns>
        public static Window FindRootWindow(DependencyObject leaveItem)
        {
            DependencyObject current = leaveItem;
            DependencyObject t = null;
            while ((t = VisualTreeHelper.GetParent(current)) != null)
            {
                current = t;
            }
            return (Window)current;
        }
    }

    /// <summary>
    /// Class to build a chain of filters for the VisualTree walker.
    /// </summary>
    public class PredicateAction
    {
        readonly Predicate<object> _predicate;
        readonly Action<object> _action;
        PredicateAction _next;

        private PredicateAction (Predicate<object> predicate, Action<object> action)
	    {
            this._predicate = predicate;
            this._action = action;
	    }

        /// <summary>
        /// Create a PredicateAction pair where the action is called, when the current instance is of type TType.
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public static PredicateAction FromType<TType>(Action<TType> action)
            where TType : class
        {
            return new PredicateAction((i) => i is TType, (i) => action((TType)i));
        }

        /// <summary>
        /// Appends a new PredicateAction to the end of the filter chain.
        /// </summary>
        /// <param name="paction"></param>
        /// <returns></returns>
        public PredicateAction Append(PredicateAction paction)
        {
            this._next = paction;
            return this._next;
        }

        internal bool Execute(DependencyObject current)
        {
            PredicateAction currentAction = this;
            do
            {
                if (currentAction._predicate(current))
                {
                    currentAction._action(current);
                    return true;
                }
                currentAction = currentAction._next;
            }
            while (currentAction != null);
            return false;
        }
    }
}
